﻿namespace TestTool.HttpTransport
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading;
    using TestTool.HttpTransport.Interfaces;
    using TestTool.HttpTransport.Interfaces.Exceptions;
    using TestTool.HttpTransport.Internals.Http;

    internal class HttpRequestChannel : ChannelBase, IRequestChannel, IChannel, ICommunicationObject
    {
        private BufferManager _bufferManager;
        private ChannelParameterCollection _channelParameters;
        private ICredentialsProvider _credentialsProvider;
        private Message _currentMessage;
        private byte[] _currentMessageBytes;
        private HttpPacket _digestAuthChallenge;
        private MessageEncoder _encoder;
        private IEndpointController _endpointController;
        private IExecutionController _executionController;
        private HttpChannelFactory _factory;
        private List<ITrafficListener> _listeners;
        private RequestNetworkStream _networkStream;
        private EndpointAddress _to;
        private Uri _via;
        private IWsaController _wsaController;

        internal HttpRequestChannel(HttpChannelFactory factory, EndpointAddress to, Uri via, MessageEncoder encoder, TestTool.HttpTransport.HttpTransportBindingElement bindingElement) : base(factory)
        {
            if (to == null)
            {
                throw new ArgumentNullException("to");
            }
            this._factory = factory;
            this._to = to;
            this._via = via;
            this._encoder = encoder;
            this._listeners = new List<ITrafficListener>();
            foreach (IChannelController controller in bindingElement.Controllers)
            {
                if (controller is ITrafficListener)
                {
                    this._listeners.Add((ITrafficListener) controller);
                }
                if (controller is IEndpointController)
                {
                    if (this._endpointController != null)
                    {
                        throw new ApplicationException("Only one channel controller of type IAddressProvider can be set");
                    }
                    this._endpointController = (IEndpointController) controller;
                }
                if (controller is IExecutionController)
                {
                    if (this._executionController != null)
                    {
                        throw new ApplicationException("Only one channel controller of type IExecutionController can be set");
                    }
                    this._executionController = (IExecutionController) controller;
                }
                if (controller is ICredentialsProvider)
                {
                    this._credentialsProvider = controller as ICredentialsProvider;
                }
                if (controller is IWsaController)
                {
                    this._wsaController = controller as IWsaController;
                }
            }
            this._networkStream = new RequestNetworkStream(to);
            this._bufferManager = BufferManager.CreateBufferManager(bindingElement.MaxBufferPoolSize, (int) bindingElement.MaxReceivedMessageSize);
        }

        public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state)
        {
            return this.BeginRequest(message, base.DefaultSendTimeout, callback, state);
        }

        public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state)
        {
            throw new NotImplementedException();
        }

        private byte[] CreateRequest(Message message, out int bodyOffset)
        {
            byte[] buffer;
            if (this._currentMessageBytes == null)
            {
                ArraySegment<byte> segment = this.EncodeMessage(message);
                buffer = new byte[segment.Count];
                Array.Copy(segment.Array, buffer, segment.Count);
                this._currentMessageBytes = buffer;
            }
            else
            {
                buffer = this._currentMessageBytes;
            }
            string s = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
            string username = string.Empty;
            string password = string.Empty;
            HttpPacket packet = null;
            DigestTestingSettings testingSettings = null;
            if (this._credentialsProvider != null)
            {
                username = this._credentialsProvider.Username;
                password = this._credentialsProvider.Password;
                if ((this._credentialsProvider.Security == Security.Digest) || (this._credentialsProvider.Security == Security.DigestTesting))
                {
                    packet = this._digestAuthChallenge;
                }
                if (this._credentialsProvider.Security == Security.DigestTesting)
                {
                    testingSettings = this._credentialsProvider.DigestTestingSettings;
                }
            }
            byte[] sourceArray = HttpHelper.CreateHttpHeaders((long) (buffer.Length + s.Length), this._to.Uri.PathAndQuery, this._to.Uri.Host, packet, username, password, testingSettings);
            byte[] destinationArray = new byte[(sourceArray.Length + buffer.Length) + s.Length];
            Array.Copy(sourceArray, destinationArray, sourceArray.Length);
            Array.Copy(Encoding.UTF8.GetBytes(s), 0, destinationArray, sourceArray.Length, s.Length);
            Array.Copy(buffer, 0, destinationArray, sourceArray.Length + s.Length, buffer.Length);
            bodyOffset = sourceArray.Length;
            return destinationArray;
        }

        private ArraySegment<byte> EncodeMessage(Message message)
        {
            ArraySegment<byte> segment;
            try
            {
                segment = this._encoder.WriteMessage(message, 0x7fffffff, this._factory.BufferManager);
            }
            finally
            {
                message.Close();
            }
            return segment;
        }

        public Message EndRequest(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        ~HttpRequestChannel()
        {
            if (base.State == CommunicationState.Opened)
            {
                base.Close();
            }
        }

        public override T GetProperty<T>() //where T: class
        {
            if (typeof(T) != typeof(ChannelParameterCollection))
            {
                if (typeof(T) == typeof(IRequestChannel))
                {
                    return (this as T);
                }
                T property = base.GetProperty<T>();
                if (property != null)
                {
                    return property;
                }
                return default(T);
            }
            if (base.State == CommunicationState.Created)
            {
                lock (base.ThisLock)
                {
                    if (this._channelParameters == null)
                    {
                        this._channelParameters = new ChannelParameterCollection();
                    }
                }
            }
            return (this._channelParameters as T);
        }

        private void GetResponse(MemoryStream responseStream, out HttpPacket header, int readTimeout)
        {
            int count = 0;
            byte[] buffer = new byte[0x800];
            bool flag = false;
            int millisecondsTimeout = readTimeout;
            DateTime now = DateTime.Now;
            do
            {
                WaitHandle[] handleArray;
                IAsyncResult result = this._networkStream.BeginRead(buffer, 0, buffer.Length);
                if ((this._executionController != null) && (this._executionController.StopEvent != null))
                {
                    handleArray = new WaitHandle[] { result.AsyncWaitHandle, this._executionController.StopEvent };
                }
                else
                {
                    handleArray = new WaitHandle[] { result.AsyncWaitHandle };
                }
                switch (WaitHandle.WaitAny(handleArray, millisecondsTimeout))
                {
                    case 0x102:
                        this._networkStream.Close();
                        throw new TestTool.HttpTransport.Interfaces.Exceptions.TimeoutException("The HTTP request has exceeded the allotted timeout");

                    case 1:
                        this._networkStream.Close();
                        this._executionController.ReportStop();
                        break;
                }
                count = this._networkStream.EndRead(result);
                DateTime time2 = DateTime.Now;
                responseStream.Write(buffer, 0, count);
                TimeSpan span = (TimeSpan) (time2 - now);
                int totalMilliseconds = (int) span.TotalMilliseconds;
                try
                {
                    flag = HttpHelper.ContinueReading(responseStream, out header);
                }
                catch (Exception exception)
                {
                    this._networkStream.Close();
                    throw new Exception("An error occurred while parsing HTTP packet", exception);
                }
                millisecondsTimeout = readTimeout - totalMilliseconds;
                if (millisecondsTimeout < 0)
                {
                    this._networkStream.Close();
                    throw new IOException("The HTTP request has exceeded the allotted timeout");
                }
            }
            while (this._networkStream.DataAvailable || flag);
        }

        private WSMessageEncoding GetTypeOfEncoding(string contentType)
        {
            if (!Regex.IsMatch(contentType, "^multipart.*"))
            {
                return WSMessageEncoding.Text;
            }
            return WSMessageEncoding.Mtom;
        }

        private void LogResponse(MemoryStream responseStream, HttpPacket header)
        {
            string formattedMessage = HttpHelper.GetFormattedMessage(responseStream.GetBuffer(), header.BodyOffset);
            foreach (ITrafficListener listener in this._listeners)
            {
                listener.LogResponse(formattedMessage);
            }
        }

        protected override void OnAbort()
        {
            this._networkStream.Close();
        }

        protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
        {
            throw new NotImplementedException();
        }

        protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
        {
            throw new NotImplementedException();
        }

        protected override void OnClose(TimeSpan timeout)
        {
            this._networkStream.Close();
        }

        protected override void OnEndClose(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        protected override void OnEndOpen(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        protected override void OnOpen(TimeSpan timeout)
        {
            this._networkStream.Connect();
        }

        private Message ReadMessage(MemoryStream responseStream, HttpPacket header)
        {
            WSMessageEncoding typeOfEncoding = this.GetTypeOfEncoding(header.ContentType);
            switch (typeOfEncoding)
            {
                case WSMessageEncoding.Text:
                    return this.ReadMessage(responseStream, header, header.BodyOffset, ((int) responseStream.Length) - header.BodyOffset);

                case WSMessageEncoding.Mtom:
                {
                    MemoryStream stream = new MemoryStream();
                    StreamWriter writer = new StreamWriter(stream);
                    MemoryStream stream2 = new MemoryStream(responseStream.GetBuffer());
                    StreamReader reader = new StreamReader(stream2);
                    bool flag = false;
                    while (!reader.EndOfStream)
                    {
                        string str = reader.ReadLine();
                        if (!flag)
                        {
                            bool flag2 = str.StartsWith("Content-Type");
                            if (string.IsNullOrEmpty(str))
                            {
                                flag2 = true;
                                flag = true;
                            }
                            if (!string.IsNullOrEmpty(str) && string.IsNullOrEmpty(str.Trim(new char[1])))
                            {
                                flag2 = true;
                                flag = true;
                            }
                            if (flag2)
                            {
                                writer.WriteLine(str);
                            }
                        }
                        else
                        {
                            writer.WriteLine(str);
                        }
                    }
                    reader.Close();
                    writer.Flush();
                    stream.Seek(0L, SeekOrigin.Begin);
                    return this.ReadMessage(stream, header, 0, (int) stream.Length);
                }
            }
            throw new NotSupportedException(string.Format("\"{0}\" message encoding are not supported.", typeOfEncoding));
        }

        private Message ReadMessage(MemoryStream responseStream, HttpPacket header, int start, int count)
        {
            Message message = null;
            if (start >= 0)
            {
                byte[] destinationArray = this._bufferManager.TakeBuffer(count);
                Array.Copy(responseStream.GetBuffer(), start, destinationArray, 0, count);
                responseStream.Close();
                message = this._encoder.ReadMessage(new ArraySegment<byte>(destinationArray, 0, count), this._bufferManager, header.ContentType);
                this._bufferManager.ReturnBuffer(destinationArray);
            }
            else
            {
                responseStream.Close();
                throw new ProtocolException(string.Format("The server returned unexpected reply: {0} {1}", header.StatusCode, header.StatusDescription));
            }
            if (this._wsaController != null)
            {
                this._wsaController.Validate(message);
            }
            return message;
        }

        public Message Request(Message message)
        {
            return this.Request(message, base.DefaultSendTimeout);
        }

        public Message Request(Message message, TimeSpan timeout)
        {
            MemoryStream stream;
            if (message == null)
            {
                throw new ArgumentNullException("message");
            }
            if (timeout < TimeSpan.Zero)
            {
                throw new ArgumentOutOfRangeException("timeout");
            }
            this._currentMessage = message;
            if (this._endpointController != null)
            {
                this._to = this._endpointController.Address;
            }
            if (this._wsaController != null)
            {
                this._wsaController.ProcessRequest(message);
                message.Headers.To = this._endpointController.Address.Uri;
            }
            else
            {
                message.Headers.Action = null;
                message.Headers.ReplyTo = null;
                message.Headers.MessageId = null;
            }
            this._networkStream.EnsureOpen(this._to);
            base.ThrowIfDisposedOrNotOpen();
            this._currentMessageBytes = null;
            this.WriteMessageToStream(message);
            int totalMilliseconds = (int) timeout.TotalMilliseconds;
            HttpPacket header = null;
            do
            {
                stream = new MemoryStream();
                this.GetResponse(stream, out header, totalMilliseconds);
            }
            while (header.StatusCode == 100);
            bool flag = false;
            if (this._credentialsProvider != null)
            {
                flag = (this._credentialsProvider.Security == Security.Digest) || (this._credentialsProvider.Security == Security.DigestTesting);
            }
            if (header.StatusCode == 0x191)
            {
                if (!flag)
                {
                    this._networkStream.Close();
                    this.LogResponse(stream, header);
                    throw new AccessDeniedException("Access denied (HTTP status 401 received)");
                }
                foreach (string str in header.Connection)
                {
                    if (StringComparer.InvariantCultureIgnoreCase.Compare(str, "close") == 0)
                    {
                        this._networkStream.Close();
                        this._networkStream.EnsureOpen(this._to);
                        break;
                    }
                }
                this._digestAuthChallenge = header;
                this.WriteMessageToStream(message);
                do
                {
                    stream = new MemoryStream();
                    this.GetResponse(stream, out header, totalMilliseconds);
                }
                while (header.StatusCode == 100);
                if (header.StatusCode == 0x191)
                {
                    this.LogResponse(stream, header);
                    this._networkStream.Close();
                    throw new AccessDeniedException("Digest authentication FAILED (HTTP status 401 received)");
                }
            }
            foreach (string str2 in header.Connection)
            {
                if (StringComparer.InvariantCultureIgnoreCase.Compare(str2, "close") == 0)
                {
                    this._networkStream.Close();
                    break;
                }
            }
            int num2 = ((int) stream.Length) - header.BodyOffset;
            this.LogResponse(stream, header);
            if (header.ContentLength < num2)
            {
                if (header.Headers.ContainsKey("Content-Length"))
                {
                    throw new HttpProtocolException(string.Format("An error occurred while receiving packet. Expected length: {0}, received: {1}", header.ContentLength, num2));
                }
                if (!header.NoBodySupposed)
                {
                    if (header.StatusCode != 200)
                    {
                        throw new HttpProtocolException(string.Format("An error returned. Error code: {0}, error description: {1}", header.StatusCode, header.StatusDescription));
                    }
                    throw new HttpProtocolException("Content-Length header is missing");
                }
            }
            HttpHelper.ValidateHttpHeaders(header);
            return this.ReadMessage(stream, header);
        }

        private void WriteMessageToStream(Message message)
        {
            int num;
            byte[] buffer = this.CreateRequest(message, out num);
            this._networkStream.Write(buffer, 0, buffer.Length);
            string formattedMessage = HttpHelper.GetFormattedMessage(buffer, num);
            foreach (ITrafficListener listener in this._listeners)
            {
                listener.LogRequest(formattedMessage);
            }
        }

        public EndpointAddress RemoteAddress
        {
            get
            {
                return this._to;
            }
        }

        public Uri Via
        {
            get
            {
                return this._via;
            }
        }
    }
}

